<?php
include_once("config.php");
// story table constants
define("S_TABLE", "story");
define("S_ID", "id");
define("S_BEGIN", "begin");
define("S_END", "end");
define("S_TITLE", "title");

// node table constants
define("TABLE", "node");
define("NODE_ID", "id");
define("LEFT_NODE_ID", "left_id");
define("RIGHT_NODE_ID", "right_id");
define("NODE_CONTENT", "content");
define("NODE_DEPTH", "depth");
define("AUTHOR_IP", "author_ip");
define("STORY", "story");
define("POST_TIME", "post_time");

// queue table constants
define("Q_TABLE", "queue");
define("Q_ID", "id");
define("Q_LEFT_NODE_ID", "left_id");
define("Q_RIGHT_NODE_ID", "right_id");
define("Q_LOCKED_UNTIL", "locked_until");
define("Q_USER_IP", "user_ip");
define("Q_NODE_DEPTH", "depth");
define("Q_STORY", "story");
define("Q_LOCK_DURATION", 10*60);

class Model
{	
	// the database
	private $db = NULL;

	// this is boring, yo
	public function __construct() {
		$this->db = new PDO("mysql:host=localhost;dbname=".DB_NAME, DB_USER, DB_PASSWORD);
		$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); 
	}


	// story functions
	public function get_story($id) {
		$sql = 'select * from '.S_TABLE.' where '.S_ID.'=:id;';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $id, PDO::PARAM_INT);
		$q->execute();
		return $q->fetch();
	}


	// node functions
	public function get_node($id) {
		$sql = 'select * from '.TABLE.' where '.NODE_ID.'=:id;';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $id, PDO::PARAM_INT);
		$q->execute();
		return $q->fetch();
	}

	// get the node between two other nodes
	public function get_middle($l, $r) {
		$sql = 'select * from '.TABLE.' where '.LEFT_NODE_ID.'=:left and '.RIGHT_NODE_ID.'=:right;';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $l, PDO::PARAM_INT);
		$q->bindParam('right', $r, PDO::PARAM_INT);
		$q->execute();
		return $q->fetch();
	}

	private function unix_time_to_str($time) {
		return date("Y-m-d h:i:s", $time);
	}

	// queue functions
	public function get_random_from_queue($story_id) {
		
		// if you currently have an item on the queue, remove it
		$sql = 'update '.Q_TABLE.' set '.Q_LOCKED_UNTIL.'=null, '.Q_USER_IP.'=null where '.Q_ID.'=:id and '.Q_LOCKED_UNTIL.'=:lock;';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $_COOKIE[Q_ID], PDO::PARAM_INT);
		$q->bindParam('lock',$_COOKIE[Q_LOCKED_UNTIL], PDO::PARAM_STR);
		$q->execute();	

		// find a free queue item
		$sql = 'select * from '.Q_TABLE.' where '.Q_STORY.'=:story and ('.Q_LOCKED_UNTIL.' is null or '.Q_LOCKED_UNTIL.' < :unix_now) order by '.Q_NODE_DEPTH.';';
		$q = $this->db->prepare($sql);
		$q->bindParam('story', $story_id, PDO::PARAM_INT);
		$q->bindParam('unix_now', $this->unix_time_to_str(time()), PDO::PARAM_INT);
		$q->execute();

		// select a weighted random value from the array
		$rows = $q->fetchAll();
		$count = count($rows);	
		$random = $this->get_weighted_random($count);
		
		$row = $rows[$random];

		// try to edit
		$row[Q_LOCKED_UNTIL] = $this->try_to_dequeue($row[Q_ID]);
		
		// set the cookie
		setcookie(Q_ID, $row[Q_ID]);
		setcookie(Q_LOCKED_UNTIL, $row[Q_LOCKED_UNTIL]);

		return $row;
	}

	// get the weighted random
	private function get_weighted_random($count) {
		$rubbish_way_of_doing_this = array();
		for($i=0; $i<$count; $i++)
		for($j=0; $j<=$i; $j++) {
			$rubbish_way_of_doing_this[]= $j;
		}
	
		return $rubbish_way_of_doing_this[rand(0,count($rubbish_way_of_doing_this)-1)];
	}

// try to dequeue an item from the free queue
	private function try_to_dequeue($id) {
		$this->db->beginTransaction();	
		// sanity check - is this token really available?
		$sql = 'select * from '.Q_TABLE.' where '.Q_ID.'=:id and ('.Q_LOCKED_UNTIL.' is null or '.Q_LOCKED_UNTIL.' < :unix_now);';	
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $id, PDO::PARAM_INT);
		$q->bindParam('unix_now', $this->unix_time_to_str(time()), PDO::PARAM_STR);
		$q->execute();
		$row = $q->fetch();

		// if not available - this won't work
		if ($row == null) {
			$this->db->rollback();
			throw new Exception("Could not dequeue");
			return;
		}
		
		// get the new time and edit token
		$lock_time = time()+Q_LOCK_DURATION;
		$time_str = $this->unix_time_to_str($lock_time);

		$user_ip = $_SERVER["REMOTE_ADDR"];
		
		// set the lock
		$sql = 'update '.Q_TABLE.' set '.Q_LOCKED_UNTIL.'=:time, '.Q_USER_IP.'=:ip where '.Q_ID.'=:id';
		$q = $this->db->prepare($sql);
		$q->bindParam('time', $time_str, PDO::PARAM_STR);
		$q->bindParam('id', $id, PDO::PARAM_INT);
		$q->bindParam('ip', $user_ip, PDO::PARAM_STR);

		$q->execute();

		$this->db->commit();

		return $time_str;
	}

	private function validate_content($content) {
		$content = strip_tags($content);

		if (strlen($content)<20 || strlen($content>500)) {
			// do something
		}

		$content = str_replace('\n', '<br>', $content);
		return $content;
	}

	// try to add
	public function try_to_add($id, $lock, $content) {
		$content = $this->validate_content($content);
		
		// try to add
		$this->db->beginTransaction();

		// sanity check - is this token the same as the one you got?
		$sql = 'select * from '.Q_TABLE.' where '.Q_ID.'=:id and '.Q_LOCKED_UNTIL.'=:lock and not '.Q_LOCKED_UNTIL.'<:time;';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $id, PDO::PARAM_INT);
		$q->bindParam('lock', $lock, PDO::PARAM_INT);
		$q->bindParam('time',$this->unix_time_to_str(time()), PDO::PARAM_STR);

		$q->execute();
		$row = $q->fetch();

		// if not available - this won't work
		if ($row == null) {
			$this->db->rollback();
			throw new Exception("Invalid edit token");
		}
		$left = $row[Q_LEFT_NODE_ID];
		$right = $row[Q_RIGHT_NODE_ID];
		$story = $row[Q_STORY];

		// get the max depth
		$sql = 'select max(depth) from '.TABLE.' where '.NODE_ID.'=:left or '.NODE_ID.'=:right;'; 
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $left, PDO::PARAM_INT);
		$q->bindParam('right', $right, PDO::PARAM_INT);
		$q->execute();
		$row = $q->fetch();
		$depth = $row[0];

		// insert into node
		$sql = 'insert into '.TABLE.'('.LEFT_NODE_ID.','.RIGHT_NODE_ID.','.NODE_CONTENT.','.NODE_DEPTH.','.AUTHOR_IP.','.POST_TIME.','.STORY.') values(:left,:right,:content,:depth+1,:ip,:time,:story);';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $left, PDO::PARAM_INT);
		$q->bindParam('right', $right, PDO::PARAM_INT);
		$q->bindParam('content', $content, PDO::PARAM_INT);
		$q->bindParam('depth', $depth, PDO::PARAM_INT);
		$q->bindParam('ip', $_SERVER['REMOTE_ADDR'], PDO::PARAM_STR);
		$q->bindParam('time', $this->unix_time_to_str(time()), PDO::PARAM_STR);
		$q->bindParam('story', $story, PDO::PARAM_STR); 
		$q->execute();
		$middle = $this->db->lastInsertId();

		// delete from the queue
		$sql = 'delete from '.Q_TABLE.' where '.Q_ID.'=:id';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $id, PDO::PARAM_INT);
		$q->execute();

		// insert into queue
		$sql = 'insert into '.Q_TABLE.'('.Q_LEFT_NODE_ID.','.Q_RIGHT_NODE_ID.','.Q_NODE_DEPTH.','.Q_STORY.') values(:left,:right,:depth+2,:story);';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $left, PDO::PARAM_INT);
		$q->bindParam('right', $middle, PDO::PARAM_INT);
		$q->bindParam('depth', $depth, PDO::PARAM_INT);
		$q->bindParam('story', $story, PDO::PARAM_INT);
		$q->execute();

		// insert into queue
		$sql = 'insert into '.Q_TABLE.'('.Q_LEFT_NODE_ID.','.Q_RIGHT_NODE_ID.','.Q_NODE_DEPTH.','.Q_STORY.') values(:left,:right,:depth+2,:story);';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $middle, PDO::PARAM_INT);
		$q->bindParam('right', $right, PDO::PARAM_INT);
		$q->bindParam('depth', $depth, PDO::PARAM_INT);
		$q->bindParam('story', $story, PDO::PARAM_INT);
		$q->execute();

		// it is done!
		$this->db->commit();
	}

	

	public function delete_children($node) {
		// get this node
		$sql = 'select * from '.TABLE.' where '.LEFT_NODE_ID.'=:left or '.RIGHT_NODE_ID.'=:right;';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $node, PDO::PARAM_INT);
		$q->bindParam('right', $node, PDO::PARAM_INT);
		$q->execute();

		$rows = $q->fetchAll();
		foreach ($rows as $row) {
			$this->delete_children($row[NODE_ID]);
		}

		echo $node;
		// delete from node table
		$sql = 'delete from '.TABLE.' where '.NODE_ID.'=:id;';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $node, PDO::PARAM_INT);
		$q->execute();

		// delete from queue
		$sql = 'delete from '.Q_TABLE.' where '.Q_LEFT_NODE_ID.'=:left or '.Q_RIGHT_NODE_ID.'=:right;';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $node, PDO::PARAM_INT);
		$q->bindParam('right', $node, PDO::PARAM_INT);

		$q->execute();
	
	}

	public function delete_node($node) {
		// start the delete
		$this->db->beginTransaction();
	
		echo $node;	
		// get this node
		$sql = 'select * from '.TABLE.' where '.NODE_ID.'=:id;';
		$q = $this->db->prepare($sql);
		$q->bindParam('id', $node, PDO::PARAM_INT);
		$q->execute();
		$row = $q->fetch();
		$left = $row[LEFT_NODE_ID];
		$right = $row[RIGHT_NODE_ID];
		$depth = $row[NODE_DEPTH];

		// delete all the kids - yo!
		$this->delete_children($node);
		
		// insert this item back into the queue ;)
		$sql = 'insert into '.Q_TABLE.'('.Q_LEFT_NODE_ID.','.Q_RIGHT_NODE_ID.','.Q_NODE_DEPTH.') values(:left,:right,:depth);';
		$q = $this->db->prepare($sql);
		$q->bindParam('left', $left, PDO::PARAM_INT);
		$q->bindParam('right', $right, PDO::PARAM_INT);
		$q->bindParam('depth', $depth, PDO::PARAM_INT);
		$q->execute();

		// commit this delete
		$this->db->commit();
	}
	
	public function get_random_node($story_id)
	{
		$sql = 'select * from '.TABLE.' where '.STORY.'=:story order by rand() limit 1;';
		$q = $this->db->prepare($sql);
		$q->bindParam('story', $story_id, PDO::PARAM_INT);
		$q->execute();
		return $q->fetch();
	}
	
	public function getNextNode($currentNode)
	{
	$sql = 'select * from '.TABLE.' where '.LEFT_NODE_ID.'=:left or '.NODE_ID.'=:right order by rand() limit 1;';
	$q = $this->db->prepare($sql);
	$q->bindParam('left', $currentNode[NODE_ID], PDO::PARAM_INT);
	$q->bindParam('right', $currentNode[RIGHT_NODE_ID], PDO::PARAM_INT);
	$q->execute();
	return $q->fetch();
	}
}

$model = new Model();

?>
